package com.smartwebx.fr.controller;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.ibatis.io.Resources;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.PrettyXmlSerializer;
import org.htmlcleaner.TagNode;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.aspose.words.Document;
import com.aspose.words.DocumentBuilder;
import com.aspose.words.DocumentVisitor;
import com.aspose.words.License;
import com.aspose.words.Paragraph;
import com.aspose.words.Run;
import com.aspose.words.SaveFormat;
import com.aspose.words.Section;
import com.aspose.words.Shape;
import com.aspose.words.Table;
import com.aspose.words.VerticalAlignment;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.smartwebx.fr.bean.ExportFile;
import com.smartwebx.fr.bean.SmartTable;
import com.smartwebx.fr.service.ckeditor.SmartChartService;
import com.smartwebx.fr.util.AsponTableUtil;
import com.smartwebx.fr.util.FindMatchedNodes;
import com.smartwebx.fr.util.StationFinalFields;

@Controller
@RequestMapping("/exportReport")
public class ExportReportController {
	@Autowired
	private HttpServletRequest request;

	@Autowired
	private SmartChartService smartChartService;

	private boolean checkLicense;

	public ExportReportController() {
		checkLicense = false;
		try {
			InputStream is = Resources
					.getResourceAsStream("\\aspose_license.xml");
			License aposeLic = new License();
			aposeLic.setLicense(is);
			is.close();
			checkLicense = true;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@RequestMapping("/exportPdf")
	public ResponseEntity<byte[]> exportPdf(ExportFile export) throws Exception {
		return export(export, SaveFormat.PDF);
	}

	@RequestMapping("/exportDocx")
	public ResponseEntity<byte[]> exportDocx(ExportFile export)
			throws Exception {
		return export(export, SaveFormat.DOCX);
	}

	private ResponseEntity<byte[]> export(ExportFile export, int saveFormat)
			throws Exception {

		String suffix = "";
		if (saveFormat == 20) {
			suffix = ".docx";
		} else if (saveFormat == 40) {
			suffix = ".pdf";
		}

		byte[] content = doDocxByAspose(export.getPageId(),
				export.getPageHtml(), export.getChartOptions(),
				export.getSmartTableJsons(), export.getPageWidth(),
				export.getPageDirect(), saveFormat);
		String fileName = "下载" + suffix;
		if (content == null) {
			content = new byte[0];
			fileName = "导出错误" + suffix;
		}

		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
		headers.setContentDispositionFormData("attachment",
				new String(fileName.getBytes(), "iso-8859-1"));
		return new ResponseEntity<byte[]>(content, headers, HttpStatus.CREATED);
	}

	private byte[] doDocxByAspose(Integer pageId, String pageHtml,
			String chartOptions, String smartTableJsons, int width,
			int orientation, int saveFormat) throws Exception {
		if (!checkLicense) {
			return null;
		}
		pageHtml = URLDecoder.decode(pageHtml, "UTF-8");
		pageHtml = cleanHtmlByPdf(pageHtml);
		pageHtml = buildChartImg(pageId, pageHtml, chartOptions);

		Document doc = new Document();
		// 设置页面宽度，页面方向
		for (Section section : doc.getSections()) {
			section.getPageSetup().setPageWidth(width);
			section.getPageSetup().setOrientation(orientation);
		}

		DocumentBuilder builder = new DocumentBuilder(doc);
		builder.insertHtml(pageHtml, true);

		doDocxTableByAspose(smartTableJsons, doc, builder);

		FontChanger f = new FontChanger();
		doc.accept(f);

		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		doc.save(outputStream, saveFormat);
		outputStream.close();
		return outputStream.toByteArray();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private String buildChartImg(Integer pageId, String pageHtml,
			String chartOptions) throws Exception {
		if (!Strings.isNullOrEmpty(chartOptions)) {
			Map<String, String> chartIdAndOptionMap = Maps.newHashMap();

			List<String> chartIdAndOptions = Splitter.on('@')
					.omitEmptyStrings().splitToList(chartOptions);
			for (String chartOption : chartIdAndOptions) {
				List<String> idAndOption = Splitter.on('|').splitToList(
						chartOption);
				if (idAndOption.size() == 2) {
					String id = idAndOption.get(0);
					String option = idAndOption.get(1);
					option = URLDecoder.decode(option,
							StationFinalFields.DEFAULT_ENCODE);
					chartIdAndOptionMap.put(id, option);
				}
			}

			org.jsoup.nodes.Document doc = Jsoup.parse(pageHtml);
			Elements findCharts = doc.select("img.smartchart");
			if (!findCharts.isEmpty()) {
				List<MyTask> chartImgTask = Lists.newArrayList();
				for (Element chart : findCharts) {
					String option = null;
					if (chart.hasClass("empty_data")) {
						option = "empty_data";
					} else if (chart.hasClass("config_error")) {
						option = "config_error";
					} else if (chart.hasClass("req_error")) {
						option = "req_error";
					} else {
						String chartId = chart.attr("id");
						option = chartIdAndOptionMap.get(chartId);
					}
					chartImgTask.add(new MyTask(request.getRequestURL(),
							pageId, option));
				}

				int threadSize = chartImgTask.size() > 20 ? 20 : chartImgTask
						.size();
				List<String> backTask = Lists.newArrayList();
				try {
					ListeningExecutorService service = MoreExecutors
							.listeningDecorator(Executors
									.newFixedThreadPool(threadSize));
					List<ListenableFutureTask<String>> futureTask = (List) service
							.invokeAll(chartImgTask);
					ListenableFuture<List<String>> futureResult = Futures
							.successfulAsList(futureTask);
					service.shutdown();
					backTask = futureResult.get();
				} catch (Exception e) {
				}

				for (int i = 0; backTask.size() == threadSize
						&& i < findCharts.size(); i++) {
					Element imgEle = findCharts.get(i);
					String imgSrc = backTask.get(i);
					if (!Strings.isNullOrEmpty(imgSrc)) {
						imgEle.attr("src", imgSrc);
					}
				}
				pageHtml = doc.body().html();
			}
		}

		return pageHtml;
	}

	private void doDocxTableByAspose(String smartTableJsons, Document doc,
			DocumentBuilder builder) throws Exception {
		List<String> smartTables = Splitter.on('@').omitEmptyStrings()
				.splitToList(smartTableJsons);
		List<SmartTable> tableDatas = Lists.newArrayList();
		ObjectMapper mapper = new ObjectMapper();
		for (String tableJson : smartTables) {
			String deTableJson = URLDecoder.decode(tableJson, "UTF-8");
			SmartTable table = mapper.readValue(deTableJson, SmartTable.class);
			tableDatas.add(table);
		}

		for (SmartTable tableData : tableDatas) {
			String findId = tableData.getId();
			Pattern regex = Pattern.compile(findId, Pattern.CASE_INSENSITIVE);
			FindMatchedNodes findMatchedNodes = new FindMatchedNodes();
			doc.getRange().replace(regex, findMatchedNodes, false);
			List<Run> findNodes = findMatchedNodes.nodes;
			if (findNodes.size() == 1) {
				Paragraph parentRun = findNodes.get(0).getParentParagraph();
				Table newTable = AsponTableUtil.buildTable(tableData, builder);

				parentRun.getParentNode().insertAfter(newTable, parentRun);
				parentRun.remove();
			}
		}
	}

	static class FontChanger extends DocumentVisitor {
		@Override
		public int visitRun(Run run) throws Exception {
			run.getFont().setName("宋体");
			return super.visitRun(run);
		}

		@Override
		public int visitShapeStart(Shape shape) throws Exception {
			shape.setVerticalAlignment(VerticalAlignment.CENTER);
			return super.visitShapeStart(shape);
		}
	}

	private class MyTask implements Callable<String> {
		StringBuffer requestURL;
		Integer pageId;
		String option;

		MyTask(StringBuffer requestURL, Integer pageId, String option) {
			this.requestURL = requestURL;
			this.pageId = pageId;
			this.option = option;
		}

		@Override
		public String call() throws Exception {
			String randomId = UUID.randomUUID().toString();
			return smartChartService.getCreateChartImage(requestURL, pageId,
					randomId, option);
		}
	}

	/**
	 * 处理html，将html转换成标准的xhtml
	 * <p>
	 * Title: cleanHtmlByPdf
	 * </p>
	 * <p>
	 * Description:
	 * </p>
	 * 
	 * @param pageHtml
	 * @return
	 */
	private String cleanHtmlByPdf(String pageHtml) {
		HtmlCleaner cleaner = new HtmlCleaner();
		TagNode node = cleaner.clean(pageHtml);
		CleanerProperties props = cleaner.getProperties();

		return new PrettyXmlSerializer(props).getAsString(node, "utf-8");
	}

}
